package com.gmrz.uaf.protocol.v1.validaton.auth;

import com.gmrz.uaf.common.Constants;
import com.gmrz.uaf.common.GuiceUtil;
import com.gmrz.uaf.crypto.CryptoEngine;
import com.gmrz.uaf.crypto.spi.UAFAuthAlgorithmSuite;
import com.gmrz.uaf.protocol.v1.processor.TransactionContentProcessor;
import com.gmrz.uaf.protocol.v1.processor.exception.UAFErrorCode;
import com.gmrz.uaf.protocol.v1.schema.Authenticator;
import com.gmrz.uaf.protocol.v1.schema.AuthenticatorSpec;
import com.gmrz.uaf.protocol.v1.schema.ServerData;
import com.gmrz.uaf.protocol.v1.schema.SignData;
import com.gmrz.uaf.protocol.v1.validaton.Validator;
import com.gmrz.util.Convert;
import com.google.inject.Inject;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.Arrays;

public class ASMSignDataValidator implements Validator<SignData> {
	private static final Logger LOG = LogManager.getLogger(ASMSignDataValidator.class);
	private Authenticator authntr;
	private AuthenticatorSpec spec;
	private String fcpBase64;
	private CryptoEngine cryptoEngine;
	private ServerData serverData;

	public ASMSignDataValidator(Authenticator authntr, String fcpBase64, ServerData serverData) {
		this.authntr = authntr;
		this.spec = authntr.getMetadata();
		this.fcpBase64 = fcpBase64;
		this.serverData = serverData;
		GuiceUtil.getProcessorInjector().injectMembers(this);
	}

	@Inject
	public void setCryptoEngine(CryptoEngine cryptoEngine) {
		this.cryptoEngine = cryptoEngine;
	}

	public void validate(SignData signData) throws AuthSignAssertionValidationException {
		String msg = "";

		String storedAAID = this.authntr.getAAID().format();
		String dataAAID = signData.getAAID();
		if ((dataAAID == null) || (!storedAAID.equals(dataAAID))) {
			msg = "AAID value in signed data [" + dataAAID + "] doesn't matches with the value in spec[" + storedAAID
					+ "]";
			logAndThrowASAVE(msg, UAFErrorCode.PROTOCOL_AAID_VALIDATION_FAILED);
		}

		int authenticatorVersion = signData.getAuthenticatorVersion();
		if (authenticatorVersion < this.spec.getAuthenticatorVersion().intValue()) {
			msg = "Authenticator[" + dataAAID + "] version should be greater than or equal to ["
					+ this.spec.getAuthenticatorVersion() + "].";
			LOG.warn(msg);
		}

		int actualAlgo = signData.getSignAlgorithm();
		if (actualAlgo != this.spec.getAlgorithm()) {
			msg = "Invalid auth algorithm[" + actualAlgo + "] for authenticator[" + this.spec.getAAID() + "]";
			logAndThrowASAVE(msg, UAFErrorCode.PROTOCOL_ALGORITHM_NOT_FOUND);
		}

		int signCounter = signData.getSignCounter();
		int dbSignCounter = this.authntr.getSignCounter();
		if ((signCounter == 0) && (dbSignCounter == 0)) {
			LOG.debug("Sign counter is not supported by Authenticator with AAID[{}]",
					this.authntr.getAAID().format());
		} else if (signCounter < dbSignCounter) {
			msg = "Sign counter value[" + signCounter + "] is not incremented from DB value[" + dbSignCounter + "]";
			logAndThrowASAVE(msg, UAFErrorCode.PROTOCOL_INVALID_SIGN_COUNTER);
		}

		try {
			byte[] hashedFinalChallenge = null;
			// 根据断言中的待签名的原数据中的算法套件标识获取算法套件
			UAFAuthAlgorithmSuite suite = this.cryptoEngine.getAuthAlgorithmSuite(actualAlgo);
			hashedFinalChallenge = suite.hash(Convert.toUtf8(this.fcpBase64));
			byte[] hashedFCP = signData.getHashedFinalChallenge();

			if (!Arrays.equals(hashedFCP, hashedFinalChallenge)) {
				msg = "Failed to validate the FCP hash.";
				logAndThrowASAVE(msg, UAFErrorCode.PROTOCOL_FCP_HASH_VALIDATION_FAILED);
			}
		} catch (Exception e) {
			msg = UAFErrorCode.PROTOCOL_FCP_HASH_VALIDATION_FAILED.toString();
			LOG.error(msg, e);
			throw new AuthSignAssertionValidationException(UAFErrorCode.PROTOCOL_INVALID_AUTH_ASSERTION);
		}

		byte authMode = signData.getAuthenticationMode();
		if (authMode == Constants.AuthenticationMode.TXN_MODE.getValue()) {
			byte[] txHash = signData.getTransactionTextHash();
			String strHash = Convert.toBase64(txHash);
			// 校验断言中待签名的原数据的交易内容是否存在与serverdata中
			if (!TransactionContentProcessor.verify(strHash, this.serverData)) {
				msg = "Failed to validate the hash of transaction content.";
				logAndThrowASAVE(msg, UAFErrorCode.PROTOCOL_TXN_HASH_VALIDATION_FAILED);
			}
		}
	}

	void logAndThrowASAVE(String msg, UAFErrorCode ec) throws AuthSignAssertionValidationException {
		LOG.error(msg);
		throw new AuthSignAssertionValidationException(ec);
	}
}
